#pragma once

#include <process.h>

template<class T>
class CThreadHlpr
{
	// I don't think making copy of such objects make sense, so prevent it
	CThreadHlpr(const CThreadHlpr&) {}
	const CThreadHlpr& operator=(const CThreadHlpr&) {}

public:
	typedef unsigned (T::*threadFn)(CEvent& evtTerminate);

public:
	CThreadHlpr()
		: m_pDestClass(NULL)
		, m_pThreadFn(NULL)
		, m_hThread(0)
		, m_evtTerminate(FALSE, TRUE)
	{
	}
	~CThreadHlpr()
	{
		StopThread();
	}

	bool StartThread(T* pDestClass, threadFn pThreadFn, bool bThrow, bool bCreateSuspended)
	{
		_ASSERT(pDestClass != NULL);
		m_pDestClass = pDestClass;

		_ASSERT(pThreadFn != NULL);
		m_pThreadFn = pThreadFn;

		m_evtTerminate.ResetEvent();
		DWORD dwInitialFlag = bCreateSuspended? CREATE_SUSPENDED: 0;

		_ASSERT(m_hThread == NULL && _T("Thread already started no need to call this function twice for one object!"));
		m_hThread = (HANDLE)_beginthreadex(NULL, 0, &ThreadStartFunction, this, dwInitialFlag, &m_nThreadId);
		if (m_hThread == 0)
		{
			if (bThrow)
				throw CCustomException(_T("Failed to create new thread!!!"));

			return false;
		}

		return true;
	}

	void StopThread()
	{
		if(m_hThread)
		{
			m_evtTerminate.SetEvent();
			WaitForSingleObject(m_hThread, INFINITE);
			CloseHandle( m_hThread );
			m_hThread = NULL;
		}
	}

	int GetErrorCode()
	{
		return errno;
	}

	HANDLE GetThreadHandle() const
	{
		return m_hThread;
	}

	unsigned GetThreadId() const
	{
		return m_nThreadId;
	}

	CEvent& GetTerminateEvent()
	{
		return m_evtTerminate;
	}

	DWORD SuspendThread()
	{
		_ASSERT(m_hThread);
		return ::SuspendThread(m_hThread);
	}

	DWORD ResumeThread()
	{
		_ASSERT(m_hThread);
		return ::ResumeThread(m_hThread);
	}

	DWORD SetThreadPriority(int nPriority)
	{
		_ASSERT(m_hThread);
		return ::SetThreadPriority(m_hThread, nPriority);
	}

private:
	static unsigned _stdcall ThreadStartFunction(void* pParam)
	{
		CThreadHlpr* pThis = (CThreadHlpr*)pParam;
		return (pThis->m_pDestClass->*pThis->m_pThreadFn)(pThis->m_evtTerminate);
	}

private:
	CEvent		m_evtTerminate;
	unsigned	m_nThreadId;
	HANDLE		m_hThread;

	T* m_pDestClass;
	threadFn m_pThreadFn;
};